/*
 * Copyright (C) 2010-2011 Comcast Cable.  All Rights Reserved.
 */
package com.hercules.android.receiver.handlers.events.binder;

import android.view.View;

import com.hercules.android.receiver.AndroidReceiver;
import com.hercules.android.receiver.LogUtil;
import com.hercules.android.receiver.handlers.events.AndroidReceiverEventHandlersConfigurationException;
import com.hercules.android.receiver.handlers.events.EventListenerBinder;
import com.hercules.android.receiver.handlers.events.EventListenerBindingException;
import com.hercules.android.receiver.handlers.events.EventRaiser;
import com.hercules.android.receiver.handlers.events.EventsUtil;
import com.hercules.android.receiver.handlers.events.KeyMappingService;
import com.hercules.android.receiver.handlers.events.listener.KeyboardEventListener;
import com.hercules.android.receiver.handlers.events.listener.MouseEventListener;
import com.hercules.android.receiver.handlers.events.listener.ViewActivationEventListener;
import com.hercules.android.receiver.model.events.XREActivateViewEvent;
import com.hercules.android.receiver.model.events.XREKeyboardEvent;
import com.hercules.android.receiver.model.events.XREMouseEvent;
import com.hercules.android.receiver.model.objects.XREView;

import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.logging.Logger;

/**
 * <p>
 * This is the default implementation of EventListenerBinder. It simply creates the Android listener according to the
 * event type, then binds it to the passed in Android View object.
 * </p>
 * <p>
 * <strong>Thread safety: </strong> This class is thread-safe after setter configuration because it's immutable after
 * setter configuration
 * </p>
 *
 * @author mekanizumu, jaindsonvs
 * @author 7realm
 * @version 1.0
 */
public class EventListenerBinderImpl implements EventListenerBinder {
    /**
     * <p>
     * The logger used for logging.
     * </p>
     * <p>
     * Initialization and Mutability: It is final and won't change once it is initialized as part of variable
     * declaration to: Logger.getLogger(EventListenerBinderImpl.class.getName()).
     * </p>
     * <p>
     * Usage: It's is used in all public methods.
     * </p>
     */
    private static final Logger LOGGER = LogUtil.getLogger(EventListenerBinderImpl.class.getSimpleName());

    /**
     * <p>
     * A map to hold the event types valid for each kind of listener.
     * </p>
     * <p>
     * Initialization and Mutability: It is final and won't change once it is initialized.
     * </p>
     * <p>
     * Usage: It is used in checkValueIsValidConstant(String, Class<?>).
     * </p>
     */
    private static final Map<Class<?>, Set<String>> CONSTANTS = new HashMap<Class<?>, Set<String>>();
    {
        // Add valid constants for XREKeyboardEvent
        Set<String> strings = new HashSet<String>();
        strings.add("onKeyUp");
        strings.add("onKeyDown");
        strings.add("onKeyStartRepeating");
        strings.add("onPreviewKeyUp");
        strings.add("onPreviewKeyDown");
        strings.add("onPreviewKeyStartRepeating");
        strings.add("virtualKeyCode");
        strings.add("rawCode");
        strings.add("alt");
        strings.add("control");
        strings.add("shift");
        strings.add("meta");
        CONSTANTS.put(XREKeyboardEvent.class, strings);
    }
    {
        // Add valid constants for XREMouseEvent
        Set<String> strings = new HashSet<String>();
        strings.add("onMouseIn");
        strings.add("onMouseOut");
        strings.add("onMouseUp");
        strings.add("onMouseDown");
        strings.add("onMouseWheel");
        strings.add("onPreviewMouseIn");
        strings.add("onPreviewMouseOut");
        strings.add("onPreviewMouseUp");
        strings.add("onPreviewMouseDown");
        strings.add("onPreviewMouseWheel");
        CONSTANTS.put(XREMouseEvent.class, strings);
    }
    {
        // Add valid constants for XREActivateViewEvent
        Set<String> strings = new HashSet<String>();
        strings.add("onActivate");
        strings.add("onDeactivate");
        CONSTANTS.put(XREActivateViewEvent.class, strings);
    }

    /**
     * <p>
     * The KeyMappingService for getting the virtual key from raw key information.
     * </p>
     * <p>
     * Initialization and Mutability: It does not need to be initialized when the instance is created.
     * </p>
     * <p>
     * Usage: It is used in bindKeyEventListener(), setKeyMappingService(). Its value legality is checked in
     * checkInitialization() method.
     * </p>
     */
    private KeyMappingService keyMappingService;
    /**
     * <p>
     * The EventRaiser used for raising a XRE event.
     * </p>
     * <p>
     * Initialization and Mutability: It does not need to be initialized when the instance is created.
     * </p>
     * <p>
     * Usage: It is used in bindViewActivationEventListener(), bindMouseEventListener(), bindKeyEventListener(),
     * setEventRaiser(). Its value legality is checked in checkInitialization() method.
     * </p>
     */
    private EventRaiser eventRaiser;

    /**
     * <p>
     * This is the default constructor for the class.
     * </p>
     */
    public EventListenerBinderImpl() {
        // empty
    }

    /**
     * <p>
     * Bind a key event listener to an Android view.
     * </p>
     * <p>
     * AndroidAssembly1 this method was changed,
     *
     * @param xreView the XRE view that is the source of the possible event
     * @param receiver the receiver that will be used by the listener.
     * @param keyEventType the key event type to bind.
     * @param uiView the Android view where the event actually comes from.
     * @throws EventListenerBindingException if any error occurs
     * @throws IllegalArgumentException if keyEventType doesn't conform to the following legal value specification:
     * Can't be null or empty. Must be one of the constant string defined on XREKeyboardEvent. xreView is null; uiView
     * is null; receiver is null;
     */
    public void bindKeyEventListener(String keyEventType, XREView xreView, View uiView, AndroidReceiver receiver)
        throws EventListenerBindingException {
        final String methodName = "bindKeyEventListener";
        LogUtil.enter(LOGGER, this, methodName, keyEventType, xreView, uiView, receiver);

        try {
            EventsUtil.checkArgumentNull(keyEventType, "keyEventType");
            EventsUtil.checkArgumentEmpty(keyEventType, "keyEventType");
            //checkValueIsValidConstant(keyEventType, XREKeyboardEvent.class);
            EventsUtil.checkArgumentNull(xreView, "xreView");
            EventsUtil.checkArgumentNull(uiView, "uiView");
            EventsUtil.checkArgumentNull(receiver, "receiver");
        } catch (IllegalArgumentException e) {
            throw LogUtil.error(LOGGER, this, methodName, e);
        }

        try {
            // create listener
            KeyboardEventListener listener =
                new KeyboardEventListener(receiver, xreView, keyEventType, eventRaiser, keyMappingService);

            // bind it to UI view
            uiView.setOnKeyListener(listener);
        } catch (IllegalArgumentException e) {
            throw LogUtil.error(LOGGER, this, methodName, new EventListenerBindingException(
                "Error occurred while creating keyboard listener.", e));
        }

        LogUtil.exit(LOGGER, this, methodName);
    }

    /**
     * <p>
     * Bind a mouse event listener to an Android view.
     * </p>
     *
     * @param xreView the XRE view that is the source of the possible event.
     * @param receiver the receiver that will be used by the listener.
     * @param mouseEventType the mouse event type to bind.
     * @param uiView the Android view where the event actually comes from.
     * @throws EventListenerBindingException if any error occurs
     * @throws IllegalArgumentException if mouseEventType doesn't conform to the following legal value specification:
     * Can't be null or empty. Must be one of the constant string defined on XREMouseEvent. xreView is null; uiView is
     * null; receiver is null;
     */
    public void bindMouseEventListener(String mouseEventType, XREView xreView, View uiView, AndroidReceiver receiver)
        throws EventListenerBindingException {
        final String methodName = "bindMouseEventListener";
        LogUtil.enter(LOGGER, this, methodName);

        try {
            EventsUtil.checkArgumentNull(mouseEventType, "mouseEventType");
            EventsUtil.checkArgumentEmpty(mouseEventType, "mouseEventType");
            //checkValueIsValidConstant(mouseEventType, XREMouseEvent.class);
            EventsUtil.checkArgumentNull(xreView, "xreView");
            EventsUtil.checkArgumentNull(uiView, "uiView");
            EventsUtil.checkArgumentNull(receiver, "receiver");
        } catch (IllegalArgumentException e) {
            throw LogUtil.error(LOGGER, this, methodName, e);
        }

        try {
            // create listener
            MouseEventListener listener = new MouseEventListener(receiver, xreView, mouseEventType, eventRaiser);

            // bind it to UI view
            uiView.setOnTouchListener(listener);
        } catch (IllegalArgumentException e) {
            throw LogUtil.error(LOGGER, this, methodName, new EventListenerBindingException(
                "Error occurred while creating mouse listener.", e));
        }

        LogUtil.exit(LOGGER, this, methodName);
    }

    /**
     * <p>
     * Bind a view activation event listener to an Android view.
     * <p>
     *
     * @param xreView the XRE view that is the source of the possible event.
     * @param receiver the receiver that will be used by the listener.
     * @param viewActivationEventType the view activation event type to bind.
     * @param uiView the Android view where the event actually comes from.
     * @throws EventListenerBindingException if any error occurs
     * @throws IllegalArgumentException if viewActivationEventType doesn't conform to the following legal value
     * specification: Can't be null or empty. Must be one of the constant string defined on XREActivateViewEvent.
     * xreView is null; uiView is null; receiver is null;
     */
    public void bindViewActivationEventListener(String viewActivationEventType, XREView xreView, View uiView,
        AndroidReceiver receiver) throws EventListenerBindingException {
        final String methodName = "bindViewActivationEventListener";

        LogUtil.enter(LOGGER, this, methodName);

        try {
            EventsUtil.checkArgumentNull(viewActivationEventType, "viewActivationEventType");
            EventsUtil.checkArgumentEmpty(viewActivationEventType, "viewActivationEventType");
            //checkValueIsValidConstant(viewActivationEventType, XREActivateViewEvent.class);
            EventsUtil.checkArgumentNull(xreView, "xreView");
            EventsUtil.checkArgumentNull(uiView, "uiView");
            EventsUtil.checkArgumentNull(receiver, "receiver");
        } catch (IllegalArgumentException e) {
            throw LogUtil.error(LOGGER, this, methodName, e);
        }

        try {
            // create listener
            ViewActivationEventListener listener =
                new ViewActivationEventListener(receiver, xreView, viewActivationEventType, eventRaiser);

            // bind it to UI view
            uiView.setOnFocusChangeListener(listener);
        } catch (IllegalArgumentException e) {
            throw LogUtil.error(LOGGER, this, methodName, new EventListenerBindingException(
                "Error occurred while creating view activation listener.", e));
        }

        LogUtil.exit(LOGGER, this, methodName);
    }

    /**
     * <p>
     * Setter for the KeyMappingService for getting the virtual key from raw key information.
     * <p>
     *
     * @param keyMappingService the KeyMappingService for getting the virtual key from raw key information.
     */
    public void setKeyMappingService(KeyMappingService keyMappingService) {
        this.keyMappingService = keyMappingService;
    }

    public KeyMappingService getKeyMappingService() {
        return this.keyMappingService;
    }
    /**
     * <p>
     * Setter for the the EventRaiser used for raising a XRE event.
     * <p>
     *
     * @param eventRaiser the EventRaiser used for raising a XRE event.
     */
    public void setEventRaiser(EventRaiser eventRaiser) {
        this.eventRaiser = eventRaiser;
    }

    /**
     * <p>
     * This method is called right after the dependency of this class is fully injected. It checks if the injected
     * values are valid.
     * <p>
     *
     * @throws AndroidReceiverEventHandlersConfigurationException if any of the injected values is invalid.
     */
    public void checkInitialization() {
        final String methodName = "checkInitialization";
        LogUtil.enter(LOGGER, this, methodName);

        if (this.eventRaiser == null) {
            throw LogUtil.error(LOGGER, this, "checkInitialization()",
                new AndroidReceiverEventHandlersConfigurationException("Argument \"eventRaiser\" can not be null."));
        }

        if (this.keyMappingService == null) {
            throw LogUtil.error(LOGGER, this, "checkInitialization()",
                new AndroidReceiverEventHandlersConfigurationException(
                    "Argument \"keyMappingService\" can not be null."));
        }

        LogUtil.exit(LOGGER, this, methodName);
    }

    /**
     * <p>
     * Checks if an event type is valid for a given class.
     * </p>
     *
     * @param eventType the string that represents the event type.
     * @param clazz the class to test the constant against.
     */
    private void checkValueIsValidConstant(String eventType, Class<?> clazz) {
        if (!CONSTANTS.get(clazz).contains(eventType)) {
            throw new IllegalArgumentException(String.format("Value \"%s\" should be a constant string defined on %s.",
                eventType, clazz.getName()));
        }
    }
}
